#include "HelperFunctions.h"
#include <string.h>
#include <malloc.h>

FILE *outputTarget = NULL;

int isKeyword(const Token *token, TokenType expectedTokenTypeType);
int isSeparator(TokenType tokenType);
int min(int x, int y, int z);
int getStringDistance(const char *string, const char *target);

int match(TokenType tokenType, TokenType *followList)
{
	Token *t = nextToken();
	int result = 0;

	if (t)
	{
		result = t->type == tokenType;
		if (!result)
		{
			TokenType expectedTokenType[] = { tokenType, none_Token };

			// Attempt error recovery:

			// If we missed a seperator or misspelled a keyword, notify of a warning 
			// but continue parsing.
			// Otherwise go to the first token of the next variable.
			result = isSeparator(tokenType) | attemptRepairKeyword(t, tokenType);
			if (!result)
			{
				outputError(t, expectedTokenType);
				gotoFollow(followList);
			}
			else
			{
				outputWarning(t, expectedTokenType);
				backToken();
			}
		}
	}

	return result;
}

int attemptRepairKeyword(Token *token, TokenType expectedTokenType)
{
	int result = token->type == id_Token;

	if (result)
	{
		result = isKeyword(token, expectedTokenType);
		if (result)
		{
			token->type = expectedTokenType;
		}
	}

	return result;
}

// Checks whether what we have here is a misspelled keyword.
int isKeyword(const Token *token, TokenType expectedTokenType)
{	
	return getStringDistance(token->lexeme, tokenStrings[expectedTokenType]) <= 1;
}

// Checks whether the token is a seperator.
int isSeparator(TokenType tokenType)
{
	const TokenType *separators = tokenSeparators;
	int result = 0;

	for (; *separators; separators++)
	{
		if (*separators == tokenType)
		{
			result = 1;
			break;
		}
	}

	return result;
}

// Returns the minimum of 3 values.
int min(int x, int y, int z)
{
    int temp = (x < y ? x : y);
    return (temp < z ? temp : z);
}

// Retrieves edit distance between two strings.
int getStringDistance(const char *string, const char *target)
{
	int **map;
    int i, j, temp,
		stringLength = strlen(string), 
		targetLength = strlen(target);

	map = (int**)malloc(sizeof(int*) * (stringLength + 1));
	for (i = 0; i <= stringLength; i++)
	{
		map[i] = (int*)malloc(sizeof(int) * (targetLength + 1));
	}

    map[0][0] = 0;
    for (i = 1; i <= stringLength; i ++)
	{
        map[i][0] = i;
	}

    for (j = 1; j <= targetLength; j ++)
	{
        map[0][j] = j;
	}

	for (i = 1; i <= stringLength; i ++) {
		for (j = 1; j <= targetLength; j ++) {
			if (string[i - 1] == target[j - 1])
				temp = 0;
			else
				temp = 1;
			map[i][j] = min((map[i - 1][j - 1] + temp),
							(map[i][j - 1] + 1),
							(map[i - 1][j] + 1));
		}
	}
	
	temp = map[stringLength - 1][targetLength - 1];
	for (i = 0; i <= stringLength; i++)
	{
		free(map[i]);
	}

	free(map);
	return temp;
}

// Skips input until we find a token from the supplied follow list.
void gotoFollow(TokenType *followList)
{
	Token *t;
	TokenType *follows;
	int result = 0;
	
	backToken();
	if (followList)
	{
		do
		{
			follows = followList;
			t = nextToken();
			for (; *follows && t; follows++)
			{
				if (*follows == t->type)
				{
					result = 1;
					break;
				}
			}
		} while (!result && t && t->type != none_Token);

		backToken();
	}
}

void initializeOutput()
{
	outputTarget = stdout;
}

// Sets the out target, whether a file or the console
void setOutputTarget(FILE *target)
{
	outputTarget = target;
}

void outputLine(char *string)
{
	fprintf(outputTarget, "%s\n", string);
}

void outputRule(int rule)
{
	fprintf(outputTarget, "Parsing rule %d\n", rule);
}

void outputError(const Token *token, TokenType *expectedTokenTypes)
{
	outputMessage("Error", token, expectedTokenTypes);
}

void outputWarning(const Token *token, TokenType *expectedTokenTypes)
{
	outputMessage("Warning", token, expectedTokenTypes);
}

void outputMessage(const char *message, const Token *token, TokenType *expectedTokenTypes)
{
	char tokenBuffer[1000] = { '\0' };

	for (; *expectedTokenTypes; expectedTokenTypes++)
	{
		strcat(tokenBuffer, tokenStrings[*expectedTokenTypes]);
		strcat(tokenBuffer, ", ");
	}

	// Remove the last ", ".
	tokenBuffer[strlen(tokenBuffer) - 2] = '\0';
	fprintf(outputTarget, "Parsing %s: Expected token(s) '%s' actual token '%s' at [%d, %d] (line, column).\n",
		message, tokenBuffer, token->lexeme, token->lineNumber, token->columnNumber);
	fflush(outputTarget);
}